﻿//*** Guidance generated code ***//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;

using NHibernate;
using NHibernate.Criterion;
using ProjectBase.Core;

using $prjsn$.Entity;
using ProjectBase.Data;
using ProjectBase.Data.NH;

namespace $safeprojectname$
{
    public class AdditionalFilterProcessor<TEntity, TFilter> : IService
        where TEntity : BusinessObject
        where TFilter : BusinessObjectFilter
    {

        public virtual void ApplyAdditional(ICriteria criteria, TFilter filter)
        {
            var restrictions = new List<ICriterion>();
            var persistentRestrictions = new Dictionary<string, IList<ICriterion>>();

            foreach (var pair in filter.Additional)
            {
                var name = pair.Key;
                var value = pair.Value;

                if (name.IsEmpty() || value.IsNull())
                {
                    continue;
                }

                var restriction = ApplyValueTypeDataEntry(name, value);

                if (restriction.Is())
                {
                    restrictions.Add(restriction);
                    continue;
                }

                restriction = ApplyPersistentDataEntry(name, value);

                if (restriction.Is())
                {
                    restrictions.Add(restriction);
                    continue;
                }

                IList<ICriterion> innerRestrictions = null;
                if (ApplyPersistentFilterDataEntry(value, out innerRestrictions))
                {
                    persistentRestrictions[name] = innerRestrictions;
                }
            }

            // are there valueType or inner property restrictions
            var noRestriction = restrictions.IsEmpty()
                                && persistentRestrictions.IsEmpty();

            if (noRestriction)
            {
                return;
            }

            // create subcriteria
            TEntity entity = null;
            var subCriteria = criteria.CreateSubCriteria(() => entity.Additional);

            // append valueTypes
            subCriteria.Append(restrictions);

            foreach (var pair in persistentRestrictions)
            {
                var name = pair.Key;
                var innerFilterRestrictions = pair.Value;
                var persistentCriteria = subCriteria.CreateSubCriteria(name);

                persistentCriteria.Append(innerFilterRestrictions);
            }

        }


        protected virtual bool ApplyPersistentFilterDataEntry(object value, out IList<ICriterion> innerRestrictions)
        {
            innerRestrictions = null;

            var filter = value as IFilter<int>;
            if (filter.IsNull())
            {
                return false;
            }

            var filterProcessorType = typeof(IApplyFilterRestrictions<,>);
            var processorType = filterProcessorType.MakeGenericType(value.GetType(), typeof(int));

            var processor = ProjectBase.Core.Service.Factory.CreateInstance(processorType) as IApplyFilterRestrictions;
            if (processor.Is())
            {
                innerRestrictions = processor.Apply(filter);
                return true;
            }

            return false;
        }
        protected virtual ICriterion ApplyPersistentDataEntry(string name, object value)
        {
            var persistent = value as IPersistentObjectWithTypedId<int>;
            if (persistent.IsNull())
            {
                return null;
            }

            return Restrictions.Like(name, value);
        }

        protected virtual ICriterion ApplyValueTypeDataEntry(string name, object value)
        {
            var type = value.GetType();

            ICriterion restriction;

            var isApplied = TryApplyString(name, value, type, out restriction)
                            || TryApplyValueType<int>(name, value, type, out restriction)
                            || TryApplyValueType<decimal>(name, value, type, out restriction)
                            || TryApplyValueType<bool>(name, value, type, out restriction)
                            || TryApplyValueType<DateTime>(name, value, type, out restriction)
                ;

            if (!isApplied)
            {
                isApplied = TryApplyValueIsIn<int>(name, value, type, out restriction)
                            || TryApplyValueIsIn<decimal>(name, value, type, out restriction)
                            || TryApplyValueIsIn<bool>(name, value, type, out restriction)
                            || TryApplyValueIsIn<DateTime>(name, value, type, out restriction);
            }

            if (isApplied)
            {
                return restriction;
            }
            return null;
        }


        protected virtual bool TryApplyString(string name, object value, Type type, out ICriterion restriction)
        {
            restriction = null;
            if (!type.IsEquivalentTo(typeof(string)))
            {
                return false;
            }

            if (value.IsNull())
            {
                return true;
            }

            var likeValue = value.ToString();
            if (likeValue.IsEmpty() || likeValue.IsEqual(Const.Asterisk) || likeValue.IsEqual(Const.Percentage))
            {
                return true;
            }

            likeValue = likeValue.Replace(Const.Asterisk, Const.Percentage);

            restriction = value.ToString().IsEqual("null")
                              ? Restrictions.IsNull(name)
                              : Restrictions.Like(name, likeValue)
                              ;

            return true;
        }

        protected virtual bool TryApplyValueType<TValue>(string name, object value, Type type, out ICriterion restriction)
            where TValue : struct
        {
            restriction = null;
            var isCompareable = type.IsEquivalentTo(typeof(TValue))
                                || type.IsEquivalentTo(typeof(TValue?));

            if (!isCompareable)
            {
                return false;
            }

            if (value.IsNull())
            {
                return true;
            }

            var sign = Sign.Equal;
            var propertyName = name;
            if (name.EndsWith("[From]"))
            {
                propertyName = name.TrimEnd("[From]");
                sign = Sign.GreaterOrEqual;
            }
            else if (name.EndsWith("[To]"))
            {
                propertyName = name.TrimEnd("[To]");
                sign = Sign.LessOrEqual;
            }

            restriction = new SimpleExpression(propertyName, value, Const.Signs[sign], false);

            return true;
        }

        protected virtual bool TryApplyValueIsIn<TValue>(string name, object value, Type type, out ICriterion restriction)
           where TValue : struct
        {
            restriction = null;
            var list = value as IList<TValue?>;

            if (list.IsNull())
            {
                return false;
            }

            if (value.IsNull())
            {
                return true;
            }

            var propertyName = name;
            var array = list
                .Where(i => i.Is())
                .Distinct();

            var findNull = list.Any(i => i.IsNull());

            if (array.IsEmpty() && !findNull)
            {
                return true;
            }


            if (findNull)
            {
                if (array.IsEmpty())
                {
                    // just IS NULL
                    restriction = Restrictions.IsNull(propertyName);
                    return true;
                }

                // IS NULL or IN
                restriction = new OrExpression
                    (
                        Restrictions.IsNull(propertyName)
                        , Restrictions.In(propertyName, array.Cast<object>().ToArray())
                    );
                return true;
            }

            // just IN
            restriction = new InExpression(name, list.Cast<object>().ToArray());

            return true;
        }
    }
}
